<!-- Licensed under a BSD license. See license.html for license -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>WebGL - Pulling Vertices</title>
<link type="text/css" href="resources/webgl-tutorials.css" rel="stylesheet" />
</head>
<body>
<div class="description">
Get vertices from a texture instead of a buffer.
</div>
<canvas id="canvas"></canvas>
</body>
<!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="resources/webgl-utils.js"></script>
<script src="resources/m4.js"></script>
<script>
"use strict";

function main() {
  // Get A WebGL context
  /** @type {HTMLCanvasElement} */
  const canvas = document.querySelector("#canvas");
  const gl = canvas.getContext("webgl2");
  if (!gl) {
    return;
  }

  const vs = `#version 300 es
  in ivec2 positionAndTexcoordIndices;

  uniform sampler2D positionTexture;
  uniform sampler2D texcoordTexture;

  uniform mat4 u_matrix;

  out vec2 v_texcoord;

  vec4 getValueByIndexFromTexture(sampler2D tex, int index) {
    int texWidth = textureSize(tex, 0).x;
    int col = index % texWidth;
    int row = index / texWidth;
    return texelFetch(tex, ivec2(col, row), 0);
  }

  void main() {
    int positionIndex = positionAndTexcoordIndices.x;
    vec3 position = getValueByIndexFromTexture(
        positionTexture, positionIndex).xyz;
   
    // Multiply the position by the matrix.
    gl_Position = u_matrix * vec4(position, 1);

    int texcoordIndex = positionAndTexcoordIndices.y;
    vec2 texcoord = getValueByIndexFromTexture(
        texcoordTexture, texcoordIndex).xy;

    // Pass the texcoord to the fragment shader.
    v_texcoord = texcoord;
  }
  `;
  const fs = `#version 300 es
  precision highp float;

  // Passed in from the vertex shader.
  in vec2 v_texcoord;

  // The texture.
  uniform sampler2D u_texture;

  out vec4 outColor;

  void main() {
    outColor = texture(u_texture, v_texcoord);
  }
  `;

  // setup GLSL program
  const program = webglUtils.createProgramFromSources(gl, [vs, fs]);

  // look up where the vertex data needs to go.
  const posTexIndexLoc = gl.getAttribLocation(
      program, "positionAndTexcoordIndices");

  // lookup uniforms
  const matrixLoc = gl.getUniformLocation(program, "u_matrix");
  const positionTexLoc = gl.getUniformLocation(program, "positionTexture");
  const texcoordTexLoc = gl.getUniformLocation(program, "texcoordTexture");
  const u_textureLoc = gl.getUniformLocation(program, "u_texture");

  const positions = [
    -1, -1,  1,  // 0
     1, -1,  1,  // 1
    -1,  1,  1,  // 2
     1,  1,  1,  // 3
    -1, -1, -1,  // 4
     1, -1, -1,  // 5
    -1,  1, -1,  // 6
     1,  1, -1,  // 7
  ];
  const uvs = [
    0, 0,  // 0
    1, 0,  // 1
    0, 1,  // 2
    1, 1,  // 3
  ];
  const positionIndexUVIndex = [
    // front
    0, 1, // 0
    1, 3, // 1
    2, 0, // 2
    3, 2, // 3
    // right
    1, 1, // 4
    5, 3, // 5
    3, 0, // 6
    7, 2, // 7
    // back
    5, 1, // 8
    4, 3, // 9
    7, 0, // 10
    6, 2, // 11
    // left
    4, 1, // 12
    0, 3, // 13
    6, 0, // 14
    2, 2, // 15
    // top
    7, 1, // 16
    6, 3, // 17
    3, 0, // 18
    2, 2, // 19
    // bottom
    1, 1, // 20
    0, 3, // 21
    5, 0, // 22
    4, 2, // 23
  ];
  const indices = [
     0,  1,  2,   2,  1,  3,  // front
     4,  5,  6,   6,  5,  7,  // right
     8,  9, 10,  10,  9, 11,  // back
    12, 13, 14,  14, 13, 15,  // left
    16, 17, 18,  18, 17, 19,  // top
    20, 21, 22,  22, 21, 23,  // bottom
  ];

  function makeDataTexture(gl, data, numComponents) {
    // expand the data to 4 values per pixel.
    const numElements = data.length / numComponents;
    const expandedData = new Float32Array(numElements * 4);
    for (let i = 0; i < numElements; ++i) {
      const srcOff = i * numComponents;
      const dstOff = i * 4;
      for (let j = 0; j < numComponents; ++j) {
        expandedData[dstOff + j] = data[srcOff + j];
      }
    }
    const tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(
        gl.TEXTURE_2D,
        0,            // mip level
        gl.RGBA32F,   // format
        numElements,  // width
        1,            // height
        0,            // border
        gl.RGBA,      // format
        gl.FLOAT,     // type
        expandedData,
    );
    // we don't need any filtering
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    return tex;
  }

  const positionTexture = makeDataTexture(gl, positions, 3);
  const texcoordTexture = makeDataTexture(gl, uvs, 2);

  // create a vertex array to hold attribute state
  const vao = gl.createVertexArray();
  gl.bindVertexArray(vao);

  // Create a buffer for the position and UV indices
  const positionIndexUVIndexBuffer = gl.createBuffer();
  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionIndexUVIndexBuffer);
  // Put the position and texcoord indices in the buffer
  gl.bufferData(gl.ARRAY_BUFFER, new Uint32Array(positionIndexUVIndex), gl.STATIC_DRAW);

  // Turn on the position index attribute
  gl.enableVertexAttribArray(posTexIndexLoc);

  // Tell the position/texcoord index attribute how to get data out
  // of positionIndexUVIndexBuffer (ARRAY_BUFFER)
  {
    const size = 2;                // 2 components per iteration
    const type = gl.INT;           // the data is 32bit integers
    const stride = 0;              // 0 = move forward size * sizeof(type) each iteration to get the next position
    const offset = 0;              // start at the beginning of the buffer
    gl.vertexAttribIPointer(
        posTexIndexLoc, size, type, stride, offset);
  }

  // Create an index buffer
  const indexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  // Put the indices in the buffer
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

  // Create a checker texture.
  const checkerTexture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, checkerTexture);
  // Fill the texture with a 4x4 gray checkerboard.
  gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.LUMINANCE,
      4,
      4,
      0,
      gl.LUMINANCE,
      gl.UNSIGNED_BYTE,
      new Uint8Array([
        0xDD, 0x99, 0xDD, 0xAA,
        0x88, 0xCC, 0x88, 0xDD,
        0xCC, 0x88, 0xCC, 0xAA,
        0x88, 0xCC, 0x88, 0xCC,
      ]),
  );
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  function radToDeg(r) {
    return r * 180 / Math.PI;
  }

  function degToRad(d) {
    return d * Math.PI / 180;
  }

  const fieldOfViewRadians = degToRad(60);
  let modelXRotationRadians = degToRad(0);
  let modelYRotationRadians = degToRad(0);

  // Get the starting time.
  let then = 0;

  requestAnimationFrame(drawScene);

  // Draw the scene.
  function drawScene(time) {
    // convert to seconds
    time *= 0.001;
    // Subtract the previous time from the current time
    const deltaTime = time - then;
    // Remember the current time for the next frame.
    then = time;

    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);

    // Animate the rotation
    modelYRotationRadians += -0.7 * deltaTime;
    modelXRotationRadians += -0.4 * deltaTime;

    // Clear the canvas AND the depth buffer.
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // Tell it to use our program (pair of shaders)
    gl.useProgram(program);

    // Set the buffer and attribute state
    gl.bindVertexArray(vao);

    // Compute the projection matrix
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const projectionMatrix =
        m4.perspective(fieldOfViewRadians, aspect, 1, 2000);

    const cameraPosition = [0, 0, 4];
    const up = [0, 1, 0];
    const target = [0, 0, 0];

    // Compute the camera's matrix using look at.
    const cameraMatrix = m4.lookAt(cameraPosition, target, up);

    // Make a view matrix from the camera matrix.
    const viewMatrix = m4.inverse(cameraMatrix);

    const viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);

    let matrix = m4.xRotate(viewProjectionMatrix, modelXRotationRadians);
    matrix = m4.yRotate(matrix, modelYRotationRadians);

    // Set the matrix.
    gl.uniformMatrix4fv(matrixLoc, false, matrix);

    // put the position texture on texture unit 0
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, positionTexture);
    // Tell the shader to use texture unit 0 for positionTexture
    gl.uniform1i(positionTexLoc, 0);

    // put the texcoord texture on texture unit 1
    gl.activeTexture(gl.TEXTURE0 + 1);
    gl.bindTexture(gl.TEXTURE_2D, texcoordTexture);
    // Tell the shader to use texture unit 1 for texcoordTexture
    gl.uniform1i(texcoordTexLoc, 1);

    // put the checkboard texture on texture unit 2
    gl.activeTexture(gl.TEXTURE0 + 2);
    gl.bindTexture(gl.TEXTURE_2D, checkerTexture);
    // Tell the shader to use texture unit 2 for u_texture
    gl.uniform1i(u_textureLoc, 2);

    // Draw the geometry.
    gl.drawElements(gl.TRIANGLES, 6 * 6, gl.UNSIGNED_SHORT, 0);

    requestAnimationFrame(drawScene);
  }
}

main();
</script>
</html>



